"""
CPEM Registry (lib/Registry.py)   
Copyright 2010, C.J. Steele, all rights reserved.

C.J. Steele <coreyjsteele@gmail.com>

This file contains the classes and code to make the whole Registry 
system work.  There are four classes: Register, Registry, SQLRegister
and SQLRegistry.  For now, the SQL pieces will be handled by SQLite3.  
I'll write this with ADOdb so that any supported database can be used.

Register class provides a container class for the registry entries, 
    including comparison and any other operational niceities 

Registry provides methods to handle a lists of Register entries, 
    including basic features like key sorting, views, and the like.

SQLRegister class provides additional methods to the Register class 
    tosavedata to an ADOdb handle

SQLRegistry class inherits the Registry and provides for saving the 
    Registry to an SQL database.
"""
import sqlite3,os


class Register:
    """Register Class
    Provides interface for register key and value pairs, this base-
    class is extended in the *Register classes to allow writing to 
    and from various persistence mechanisms.
    """
    registerKey = ''
    registerVal = ''

    def __init__(self,key='',val=''):
        self.setKey(key)
        self.setVal(val)

    #TODO: write-in __gt__ __eq__, etc. operator overloads.
    #TODO: enforce maximum content length's here
    #TODO: have set() functions return bool values

    def setKey(self,key):
        self.registerKey = key

    def setVal(self,val):
        self.registerVal = val

    def val(self):
        return self.registerVal

    def key(self):
        return self.registerKey
'''end Register'''


class Registry:
    """Registry Class
    base class for Registry, provides basic interface for 
    maintaining lists of Register objects.  This class is 
    extended in *Registry classes to interface with various
    data persistence mechanisms"""
    registry = []

    def __init__(self):
        '''default constructor for Registry, has no pre-conditions'''
        pass

    def add(self,reg):
        ''' verify that the entry is unique before adding it to the registry'''
        if self.__isUniqueKey(reg.key()):
            self.registry.append(reg)
        else:
            '''the key isn't unique, over-write the key with the new value'''
            for regPair in self.registry:
                if regPair.key() == reg.key():
                    regPair.setVal(reg.val())
                    return True
            return False

    def remove(self,key):
        '''removes a register from the registry'''
        tmpReg = []
        count = 0
        for reg in self.registry:
            if reg.key() != key:
                tmpReg.append(reg)
                count = count + 1
        self.registry = None
        self.registry = tmpReg
        return count

    def val(self,key):
        '''gets the value of a register based on 'key', which is the 
        plain-text string name of the register'''
        for reg in self.registry:
            if reg.key() == key:
                return reg.val()

    def dump(self):
        '''this prints the key aned value elements of each register and 
        returns a count of how many registers are in the registry, this
        should be for debug purposes as it only outputs to STDOUT 
        '''
        count = 0
        for reg in self.registry:
            print('%d => %s : %s' % (count,reg.key(),reg.val()))
            count = count + 1
        return count
    '''end dump()'''


    def __check(self):
        '''check to verify that all keys are unique and have valid values'''
        for reg in self.registry:
            if not self.__isUniqueKey(reg.key()):
                return False
        return True
    '''end __check()'''


    def __isUniqueKey(self,key):
        '''checks to make sure key is a unique key in the registry'''
        for regPair in self.registry:
            if regPair.key() == key:
                return False
        return True
    '''end __isUniqueKey()'''


'''end Registry'''




class SQLRegister(Register):
    ''' inherets the Register base-class and provides additional 
    functionality for storing data persistently in an SQL database using 
    an sqlite3 connection'''
    saveNeeded = False
    dbCursor = None

    def __init__(self,key,val,dbCursor):
        self.setKey(key)
        self.setVal(val)
        self.setCursor(dbCursor)

    def setVal(self,val):
        '''set the value and toggle the save flag'''
        self.registerVal = val
        if self.registerVal is val:
            self.setSave(True)
            return True
        return False
    '''end setVal()'''


    def setCursor(self,dbCursor):
        '''verify that our cursor is valid, and set our internal attribute dbCursor'''
        try:
            if self.dbCursor.EOF:
                '''will fail if dbCursor is not valid'''
                return False
        except:
            return False
        self.dbCursor = dbCursor
        return True
    '''end setCursor()'''


    def save(self):
        '''returns a boolean state depending on success or failure of 
        the database write'''
        try:
            dbCursor.Execute("insert into registry (key,val) values ('%s','%s')" % (self.key(),self.val()))
        except(sqlite3.Error,dbErr):
            #TODO: catch a more specific error, and use UPDATE instead of INSERT if need-be
            print('E: Unable to save Register to database! ("%s")' % (dbErr[0]))
            return False
        self.setSave(False)
        return True
    '''end save()'''


    def setSave(self,state):
        '''sets the saveNeeded flag as appropriate'''
        if state:
            self.saveNeeded = True
        else:
            self.saveNeeded = False
    '''end setSave()'''

'''end SQLRegister'''




class SQLRegistry(Registry):
    """SQLRegistry 
    handles data persistence of Registry items by writing them to an 
    SQL database.  Is responsible for the initialization of the 
    database(s) as well as actually committing data to them.
    
    The DSN passed in must be of a form of either rpc://host:port or
    file://relative/path/to/file
    
    In the case of a relative file path, it will be relative to the root of 
    CPEM and cannot "back reference" (e.g. '../../var/run/cpem.db') a directory
    outside of CPEM's root.  This is a security precaution as we have a watch-
    dog plugin in both the client and server modules that monitor for insecure 
    file permissions/conditions.
    """
    dbHandle = None
    dbType = 'file'
    scope = '.'

    def __init__(self,dbDSN='file://cpem.db'):
        # dbDSN is in the form of rpc://host:port or file:///full/path/to/file or file://relative/path/to/file
        dsnType = dbDSN.partition('://')[0].lower()
        dbDSN = dbDSN.partition('://')[2]
        if dsnType is not None and dbDSN is not None:
            # we've got a clean DSN, at least elementarily.
            if dsnType is "rpc":
                # its an RPC link
                self.dbType = 'rpc'
                host,port = dbDSN.partition(':')
                if port.isdigit() is True and host is not None:
                    # meh, wtf, they appear to be putting forth some effort at least.
                    # this is a connection to a remote host
                    #TODO: um, how the hell do we actually do this?  Sweet tits, we're going to have to spawn another XMLRPC hoobadichy.
                    #  0) create a memory-only SQLite3 db
                    #  1) connect to the remote RPC server and get( '' )
                    #  2) foreach entry returned, insert it into the memory-only db
                    #  3) dbHandle will now be an SQLite database object?  
                    #  4) profit.
                    pass
                else:
                    return False
            elif dsnType is "file":
                # it appears to be a local file
                if os.path.exists(dbDSN):
                    # our file appears to exist!
                    try:
                        # setup the database connection
                        self.dbHandle = sqlite3.connect(dbDSN)
                        self.dbType = 'file'
                        #TODO: walk the database and populate our self.registry list with objects
                        return True
                    except(sqlite3.Error,dbErr):
                        print('E: oops!  something went tragically wrong while we were trying to connect to that database! ("%s")' % (dbErr[0]))
                        return False
                else:
                    # TODO: verify we can write to this before we just plow through and do it.
                    return self.__initialize(dbDSN)

        return False
    '''end __init__()'''


    def __initialize(self,dbFileName='%s%s%s' % (os.getcwd(),os.sep,'cpemdb.db')):
        if os.path.exists(dbFileName):
            '''we don't want to over-write an existing database'''
            return False
        else:
            try:
                CPEMdb = sqlite3.connect(dbFileName)
                dbCursor = CPEMdb.cursor()
                dbCursor.execute('''create table if not exists registry ( regkey text(128) not null, reg val text not null, primary key(key), unique(key) )''')
                dbCursor.execute('''insert into registry ( regkey, regval ) values ( 'root.sanityCheck', '1' )''')
            except(sqlite3.Error,dbInitErr):
                print('E:could not initialize the database file! ("%s")' % (dbInitErr[0]))
                return False
        return False
    '''end __initialize()'''


    def __del__(self):
        '''destructor, calls save() method to make sure all elements are saved'''
        try:
            self.save()
        except:
            #TODO: fix this, it needs to be able to do this gracefully... maybe put in a timer to hold off for a few seconds to save it out?  I dunno.
            pass
    '''end __del__()'''


    def destroy(self):
        '''calls a '''
        pass
    '''end destroy()'''


    def save(self):
        '''commit the registry to the database, but only the records that need to be saved!'''
        pass
    '''end save()'''


    def get(self,scope='cfg.'):
        """get()        
        """
        pass
    '''end get()'''


    def set(self,variable,value):
        pass
    '''end set()'''


'''end SQLRegistry'''
